package com.wei.czz.framework.index.service.impl;

import com.wei.czz.common.constant.RedisConstant;
import com.wei.czz.common.dto.blog.BlogDto;
import com.wei.czz.common.dto.blog.TagSortDto;
import com.wei.czz.common.exception.CzzException;
import com.wei.czz.common.tool.PageDto;
import com.wei.czz.common.utils.CopyUtils;
import com.wei.czz.common.utils.RandomNumUtils;
import com.wei.czz.framework.blog.entity.BlogEntity;
import com.wei.czz.framework.blog.service.BlogIssueService;
import com.wei.czz.framework.common.handler.ThrottleHandler;
import com.wei.czz.framework.common.handler.redis.RedisHandler;
import com.wei.czz.framework.common.handler.redis.RedisStringHandler;
import com.wei.czz.framework.common.handler.redis.RedisZSetHandler;
import com.wei.czz.common.vo.blog.TagSortVo;
import com.wei.czz.framework.index.service.TagSortService;
import lombok.AllArgsConstructor;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.data.redis.core.ZSetOperations;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

/**
 * Created by IntelliJ IDEA.
 *
 * @author wei
 * date: 2021-05-26 13:53:00
 * className: TagSortServiceImpl 浏览器页面-标签排行页面-服务接口实现类
 * version: 1.0
 * description:
 */
@Service("tagSortService")
@AllArgsConstructor
public class TagSortServiceImpl implements TagSortService {

    private static final Logger log = LoggerFactory.getLogger(TagSortServiceImpl.class);

    private final BlogIssueService blogIssueService;

    private final RedisZSetHandler redisZSetHandler;

    private final RedisStringHandler redisStringHandler;

    private final RedisHandler redisHandler;

    private final ThrottleHandler throttleHandler;

    private final RedissonClient redissonClient;

    @Override
    public List<TagSortDto> getTagList(TagSortVo tagSortVo) {
        // 获取Redis分布式锁对象
        RLock lock = redissonClient.getLock(RedisConstant.LOCK + RedisConstant.TAG_SORT_LIST);
        // 查询Redis，获取标签排行分页列表
        List<TagSortDto> tagSortList = redisStringHandler.get(RedisConstant.TAG_SORT_LIST);
        if (tagSortList != null) {
            throttleHandler.throttleRun(() -> {
                // 尝试更新缓存键的过期时间
                redisHandler.tryUpdateKeyExpire(lock, RedisConstant.TAG_SORT_LIST, 150,
                        RandomNumUtils.random5Minutes(10));
            }, RedisConstant.TAG_SORT_LIST, 1500, TimeUnit.MILLISECONDS);

            return tagSortList;
        }
        // 加锁30秒
        lock.lock();

        try {
            // 重复查询Redis，获取标签排行分页列表
            tagSortList = redisStringHandler.get(RedisConstant.TAG_SORT_LIST);
            if (tagSortList != null) {
                return tagSortList;
            }

            // 获取所有标签排序信息，按照被引用次数降序排序
            Set<ZSetOperations.TypedTuple<String>> typedTuples =
                    redisZSetHandler.reverseRangeByScoreWithScores(RedisConstant.TAG_ZSET, 0, Integer.MAX_VALUE);
            if (!typedTuples.isEmpty()) {
                tagSortList = new ArrayList<>(typedTuples.size());
                for (ZSetOperations.TypedTuple<String> typedTuple : typedTuples) {
                    String tagName = typedTuple.getValue();
                    Double score = typedTuple.getScore();
                    if (tagName == null || score == null) {
                        throw new CzzException("标签排序对象为空，请联系管理员!");
                    }
                    TagSortDto tagSortDto = new TagSortDto();
                    tagSortDto.setTagName(tagName)
                            .setNum(score.intValue());
                    // 收集返回标签结果
                    tagSortList.add(tagSortDto);
                }

                // 缓存所有标签的排序信息五分钟
                redisStringHandler.set(RedisConstant.TAG_SORT_LIST, tagSortList,
                        RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);
            }

            return tagSortList;
        } finally {
            // 判断当前锁对象是否还持有锁 并 持有该锁的线程与当前业务线程一致
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
    }

    @Override
    public PageDto<BlogDto> getBlogList(TagSortVo tagSortVo) {
        // 博客标签关联的系统已发布博客列表缓存键
        String tagBlogListKey = tagSortVo.getTagBlogListKey();
        // 获取Redis分布式锁对象
        RLock lock = redissonClient.getLock(RedisConstant.LOCK + tagBlogListKey);
        // 查询Redis缓存，获取博客标签关联的系统已发布博客列表
        PageDto<BlogDto> pageDto = redisStringHandler.get(tagBlogListKey);
        if (Objects.nonNull(pageDto)) {
            log.info("从缓存中获取到博客标签关联的已发布博客列表");
            throttleHandler.throttleRun(() -> {
                // 尝试更新缓存键的过期时间
                redisHandler.tryUpdateKeyExpire(lock, tagBlogListKey, 150,
                        RandomNumUtils.random5Minutes(10));
            }, tagBlogListKey, 1500, TimeUnit.MILLISECONDS);

            return pageDto;
        }
        // 加锁30秒
        lock.lock(30, TimeUnit.SECONDS);

        try {
            // 重复查询Redis缓存，获取博客标签关联的系统已发布博客列表
            pageDto = redisStringHandler.get(tagBlogListKey);
            if (Objects.nonNull(pageDto)) {
                log.info("double check 从缓存中获取到博客标签关联的已发布博客列表");
                return pageDto;
            }

            /*
                分页获取博客标签关联的已发布博客列表
             */
            PageDto<BlogEntity> _pageDto = blogIssueService.getTagBlogPageList(tagSortVo);
            if (_pageDto.isEmpty()) {
                pageDto = _pageDto.rebuild();
                // 缓存空结果
                redisStringHandler.set(tagBlogListKey, pageDto, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);
                return pageDto;
            }

            List<BlogEntity> blogList = _pageDto.getList();

            // 列表映射
            List<BlogDto> blogDtoList = CopyUtils.mapList(blogList, BlogDto.class);
            // 封装
            pageDto = _pageDto.rebuild(blogDtoList);

            // Redis缓存`博客标签关联的系统已发布博客列表`五分钟
            redisStringHandler.set(tagBlogListKey, pageDto, RandomNumUtils.random5Minutes(10), TimeUnit.SECONDS);

            return pageDto;
        } finally {
            // 判断当前锁对象是否还持有锁 并 持有该锁的线程与当前业务线程一致
            if (lock.isLocked() && lock.isHeldByCurrentThread()) {
                // 解锁
                lock.unlock();
            }
        }
    }

    @Async("threadExecutor")
    @Override
    public Future<PageDto<BlogDto>> getBlogListOnAsync(TagSortVo tagSortVo) {

        PageDto<BlogDto> pageDto = this.getBlogList(tagSortVo);

        return AsyncResult.forValue(pageDto);
    }

}
